Deno 와 Hono 로 CSR 하기
목차
개요
요즘 Deno 가 마음에 들어서 자주 사용하고 있는데, OSSCA 를 시작하며 Hono 라는 웹 프레임워크를 알게 되며 둘을 같이 써보고 싶어졌다.
Hono 가 JSX 도 지원하길래 간단하게 사용하면 될 줄 알았는데 CSR 설정이 생각보다 귀찮았다.
~~보통은 반대던데~~ 아무래도 Hono
가 원래 백엔드 프레임워크라 번들링에 대한 설정이 없어서 그런 것 같다.
찾아보니 Vite 나 Webpack 같은 번들러를 사용한다고 해서 Vite까지 써야하나...
하고 고민하던 중에
마침 며칠 전에 나온 Deno 2.4 에 deno bundle
명령어가 있다길래 공부할 겸 사용해봤다.
SSR 쓸거면 그냥 파일 확장자만 .tsx
로 바꾸고 JSX 로 작성하면 되니
~~그마저도 귀찮으면 그냥 html 문자열로~~ 굳이 설정할 필요가 없으므로 이 글을 읽을 필요가 없다.
앱 생성
deno init
deno add jsr:@hono/hono
혹은 Hono 공식 문서에 있는
deno init --npm hono .
명령어를 사용해도 된다. 다만 나는 위의 방법을 기준으로 설명하겠다.
deno.json
deno.json
파일이 생성되면 다음과 같이 compilerOptions
옵션을 추가/수정한다.
{
...,
"compilerOptions": {
"lib": ["esnext", "dom", "deno.ns"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
lib
: Deno에서 사용할 라이브러리의 목록을 지정한다.esnext
: 최신 ECMAScript 기능을 사용한다.dom
: DOM API를 사용할 수 있도록 한다.deno.ns
: Deno 네임스페이스를 사용한다.
jsx
: JSX 변환 방식을 지정한다.react-jsx
는 React 17 이상에서 사용하는 새로운 JSX 변환 방식을 의미한다.jsxImportSource
: JSX를 변환할 때 사용할 라이브러리를 지정한다. Hono의 JSX를 사용하기 위해hono/jsx
를 지정한다.
deno init --npm hono .
명령어를 사용하면"jsx": "precompile", "jsxImportSource": "hono/jsx"
로 설정이 돼서 번들링 시에 오류가 발생한다. 그래서 공식 문서를 뒤져서 나온"jsx": "react-jsx", "jsxImportSource": "hono/jsx/dom"
으로 설정했더니 이번엔 Deno 언어 서버가hono/jsx
에 있는JSX.IntrinsicElements
를 못 찾아서 컴포넌트마다 오류가 발생했다. 한참 검색도 해보고 LLM 도 열일 시킨 후에 결국 둘을 합친"jsx": "react-jsx", "jsxImportSource": "hono/jsx"
로 설정하니 오류가 사라졌다.
app.tsx
app.tsx
파일을 적당히 생성하자.
import { render } from "hono/jsx/dom";
function App() {
return (
<main>
<h1>Hello, Hono?!</h1>
<p onClick={() => alert("Welcome to Hono!")}>
This is a simple Hono application.
</p>
</main>
);
}
const body = document!.getElementsByTagName("body")[0];
if (!body) {
throw new Error("Root element not found");
}
render(<App />, body);
번들링
Deno는 2.4 버전부터 deno bundle
명령어를 지원한다.
deno bundle [files...]
deno bundle app.tsx
-o
옵션
이렇게만 적으면 files
에 있는 파일들을 번들링한 결과를 출력해버린다.
> <output>
으로 해도 되지만 -o, --output <output>
옵션으로 파일로 저장할 수 있다.
deno bundle app.tsx -o <output>
--platform
옵션
그리고 --platform <platform>
옵션으로 플랫폼을 지정할 수 있다.
현재는 browser
, deno
두가지가 있으며 기본값은 deno
이다.
나는 웹 서버에 사용할 것이므로 --platform browser
옵션을 사용한다.
deno bundle app.tsx --platform browser -o <output>
--minify
옵션
--minify
옵션을 사용하면 번들링된 결과를 최소화할 수 있다.
deno bundle app.tsx --platform browser --minify -o <output>
번들링 결과 예시
작업 경로에 적당한 index.html
파일을 만들고, <script>
태그에 src
속성으로 아까 번들링된 파일<output>
경로를 지정해주자.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<main id="main"></main>
<script type="module" src="<output>"></script>
</body>
</html>
이 파일을 jsr:@std/http/file-server
를 사용하여 정적 파일 서버로 제공할 수 있다.
deno run -ENR jsr:@std/http/file-server
Hono 와 함께 사용하기
이제 Hono를 사용하여 간단한 웹 서버를 만들어보자.
main.ts
파일에 다음과 같이 작성한다.
<output>
부분은 아까 번들링한 파일의 경로로 바꿔준다.
import { Hono } from 'jsr:@hono/hono';
const app = new Hono();
app.use("/<output>", serveStatic({ root: "./" }));
app.get("*", async (c) => {
try {
const html = await Deno.readTextFile("./index.html");
return c.html(html);
} catch (error) {
console.error("Error reading index.html:", error);
return c.text("Error loading page", 500);
}
});
export default app;
번들링 파일은 정적 파일로 제공하고, index.html
파일을 읽어와서 클라이언트에게 응답한다는 내용이다.
나같은 경우는 번들링을 out/bundle.js
로 저장한 뒤 아예 /out/*
경로를 정적 파일로 제공하도록 했다.
app.use("/out/*", serveStatic({ root: "./" }));
이제 main.ts
파일을 실행하면 Hono 서버가 시작된다.
deno run -A main.ts
-A
옵션은 모든 권한을 허용하는 옵션이라 보안상 주의가 필요하니 꼭 확인 후 사용하자.
deno task
이제 파일을 수정할 때마다 다음 명령어를 입력해주면 된다.
deno bundle --minify --platform=browser app.tsx -o out/bundle.js
deno run -A main.ts
당연히 귀찮다.
자동으로 번들링하고 서버를 재시작하는 deno task
를 만들어보자.
deno.json
파일에 다음과 같이 tasks
항목을 추가한다.
{
...,
"tasks": {
"dev": "deno bundle --minify --platform=browser app.tsx -o out/bundle.js && deno run -A main.ts"
}
}
그리고 다음 명령어로 실행한다.
deno task dev
--watch
문제는 이렇게 하면 파일을 수정할 때마다 서버를 껐다가 다시 켜야 한다는 점이다.
두 커멘드에 각각 --watch
옵션을 추가하면 파일이 수정될 때마다 자동으로 번들링하고 서버를 재시작한다.
{
...,
"tasks": {
"dev": "deno bundle --minify --platform=browser app.tsx -o out/bundle.js --watch && deno run -A main.ts --watch"
}
}
이 때 deno bundle ... --watch
옵션을 사용하면 파일이 수정되는지 확인하기 위해 deno bundle
명령어가 계속 실행되고 뒤의 deno run ...
명령어는 실행되지 않는다.
그래서 중간에 &&
를 &
로 바꿔줘야한다.
{
...,
"tasks": {
"dev": "deno bundle --minify --platform=browser app.tsx -o out/bundle.js --watch & deno run -A main.ts --watch"
}
}
&&
는 이전 명령어가 성공적으로 실행된 후에 다음 명령어를 실행하는 반면, &
는 이전 명령어와 상관없이 다음 명령어를 실행하므로 차이를 잘 알아뒀다가 요긴하게 써먹자.
참고로 <command> &
는 커멘드를 백그라운드에서 실행시켜놓고 다른 커멘드를 실행시킬 수 있어 필요한 경우 종종 사용하기도 한다.
이제 deno task dev
명령어를 실행하면 파일이 수정될 때마다 자동으로 번들링하고 서버를 재시작한다.